package com.chaos.auth.config.grant.provider;


import cn.hutool.core.util.ObjectUtil;
import com.chaos.auth.config.grant.converter.helper.OAuth2EndpointHelper;
import com.chaos.auth.config.grant.grant.GrantType;
import com.chaos.auth.config.grant.token.AdminPasswordAuthenticationToken;
import com.chaos.auth.config.grant.token.AdminPhoneAuthenticationToken;
import com.chaos.auth.config.userdetails.service.AdminByPhoneDetailsService;
import com.chaos.auth.config.userdetails.service.AdminByUsernameDetailsService;
import com.chaos.framework.model.enums.ResultEnum;
import com.chaos.framework.model.serivce.RedisService;
import com.chaos.framework.starter.helper.CheckHelper;
import com.chaos.system.api.enums.SmsType;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;

import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author jacky
 * @date 2025/5/9
 */

@Slf4j
public class AdminPhoneAuthenticationProvider implements AuthenticationProvider {

    @Setter
    private OAuth2AuthorizationService authorizationService;
    @Setter
    private AdminByPhoneDetailsService userDetailsService;

    @Setter
    private OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
    @Setter
    private RedisService redisService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        AdminPhoneAuthenticationToken adminPhoneAuthenticationToken = (AdminPhoneAuthenticationToken) authentication;

        // 获取客户端信息
        OAuth2ClientAuthenticationToken clientPrincipal =
                getAuthenticatedClientElseThrowInvalidClient(adminPhoneAuthenticationToken);
        RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();

        // 验证手机号，验证码
        String phone = adminPhoneAuthenticationToken.getPhone();
        String captcha = adminPhoneAuthenticationToken.getCaptcha();

        String key = String.format("phone_captcha:%s:%s", SmsType.PHONE_LOGIN.getCode(), phone);
        String redisCaptcha = (String) redisService.get(key);
        if (ObjectUtil.isNull(redisCaptcha)) {
            OAuth2EndpointHelper.throwError(ResultEnum.VERIFY_CAPTCHA_EXPIRED);
        }
        if (!redisCaptcha.equals(captcha)) {
            OAuth2EndpointHelper.throwError(ResultEnum.VERIFY_CAPTCHA_ERROR);
        }
        redisService.del(key);

        UserDetails userDetails = userDetailsService.loadUserByPhone(phone);


        if (ObjectUtil.isNull(userDetails)) {
            OAuth2EndpointHelper.throwError(ResultEnum.WRONG_PHONE);
        }

        // 创建认证对象
        Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(
                userDetails,
                null,
                userDetails.getAuthorities()
        );

        Set<String> authorizedScopes = registeredClient.getScopes();

        // 构建token上下文
        DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
                .registeredClient(registeredClient)
                .principal(usernamePasswordAuthentication)
                .authorizationServerContext(AuthorizationServerContextHolder.getContext())
                .authorizationGrantType(GrantType.ADMIN_PHONE)
                .authorizedScopes(authorizedScopes)
                .authorizationGrant(adminPhoneAuthenticationToken)
                .tokenType(OAuth2TokenType.ACCESS_TOKEN);

        // 生成访问令牌
        OAuth2TokenContext tokenContext = tokenContextBuilder.build();
        OAuth2Token generatedAccessToken = tokenGenerator.generate(tokenContext);

        if (generatedAccessToken == null) {
            OAuth2EndpointHelper.throwError(ResultEnum.GENERATE_ACCESS_TOKEN_ERROR);
        }

        // 生成访问令牌
        OAuth2AccessToken accessToken = new OAuth2AccessToken(
                OAuth2AccessToken.TokenType.BEARER,
                generatedAccessToken.getTokenValue(),
                generatedAccessToken.getIssuedAt(),
                generatedAccessToken.getExpiresAt(),
                authorizedScopes
        );

        // 生成刷新令牌
        OAuth2RefreshToken refreshToken = null;
        if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {
            tokenContextBuilder = DefaultOAuth2TokenContext.builder()
                    .registeredClient(registeredClient)
                    .principal(usernamePasswordAuthentication)
                    .authorizationServerContext(AuthorizationServerContextHolder.getContext())
                    .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                    .authorizedScopes(authorizedScopes)
                    .authorizationGrant(adminPhoneAuthenticationToken)
                    .tokenType(OAuth2TokenType.REFRESH_TOKEN);

            OAuth2Token generatedRefreshToken = tokenGenerator.generate(tokenContextBuilder.build());

            if (generatedRefreshToken != null) {
                refreshToken = new OAuth2RefreshToken(
                        generatedRefreshToken.getTokenValue(),
                        generatedRefreshToken.getIssuedAt(),
                        generatedRefreshToken.getExpiresAt()
                );
            }
        }

        // 构建授权信息
        OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
                .principalName(userDetails.getUsername())
                .authorizationGrantType(GrantType.ADMIN_PHONE)
                .authorizedScopes(authorizedScopes)
                .attribute(Principal.class.getName(), usernamePasswordAuthentication);

        if (accessToken != null) {
            authorizationBuilder.accessToken(accessToken);
        }

        if (refreshToken != null) {
            authorizationBuilder.refreshToken(refreshToken);
        }

        // 保存授权信息
        OAuth2Authorization authorization = authorizationBuilder.build();
        authorizationService.save(authorization);

        //log.debug("Saved authorization: {}", authorization.getId());

        // 增加自定义参数
        Map<String, Object> additionalParameters = new HashMap<>();
        //additionalParameters.put("authorities",
        //         userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList());

        return new OAuth2AccessTokenAuthenticationToken(
                registeredClient,
                clientPrincipal,
                accessToken,
                refreshToken,
                additionalParameters);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return AdminPhoneAuthenticationToken.class.isAssignableFrom(authentication);
    }


    private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
        OAuth2ClientAuthenticationToken clientPrincipal = null;
        if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
            clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
        }
        if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
            return clientPrincipal;
        }
        OAuth2EndpointHelper.throwError(ResultEnum.INVALID_CLIENT);
        return null;

    }
}

